package com.jivesoftware.os.amza.api.wal;
import com.google.common.primitives.Longs;
import com.google.common.primitives.UnsignedBytes;
import com.google.common.primitives.UnsignedLongs;
import com.jivesoftware.os.amza.api.filer.UIO;
import java.io.Serializable;
import java.nio.ByteOrder;
import java.util.Comparator;
import sun.misc.Unsafe;
/**
*
*/
public class KeyUtil {
private static final BestComparator INSTANCE = new BestComparator();
public static int compare(byte[] composedA, byte[] composedB) {
return INSTANCE.compare(composedA, composedB);
}
public static Comparator<byte[]> lexicographicalComparator() {
return INSTANCE;
}
private static class BestComparator implements Comparator<byte[]>, Serializable {
private static final Comparator<byte[]> BEST_COMPARATOR = getBestComparator();
@Override
public int compare(byte[] o1, byte[] o2) {
return BEST_COMPARATOR.compare(o1, o2);
}
private static Comparator<byte[]> getBestComparator() {
Comparison keyComparison = lexicographicalComparison();
int PREFIX_OFFSET = 2;
return (o1, o2) -> {
short len1 = UIO.bytesShort(o1);
short len2 = UIO.bytesShort(o2);
int c = keyComparison.compare(o1, PREFIX_OFFSET, len1, o2, PREFIX_OFFSET, len2);
if (c != 0) {
return c;
}
int keyOffset1 = PREFIX_OFFSET + len1;
int keyOffset2 = PREFIX_OFFSET + len2;
return keyComparison.compare(o1,
keyOffset1,
o1.length - keyOffset1,
o2,
keyOffset2,
o2.length - keyOffset2);
};
}
}
public interface Comparison {
int compare(byte[] o1, int offset1, int len1, byte[] o2, int offset2, int len2);
}
public static Comparison lexicographicalComparison() {
return Holder.BEST_COMPARISON;
}
private static class Holder {
private static final String UNSAFE_COMPARATOR_NAME = Holder.class.getName() + "$UnsafeComparison";
private static final Comparison BEST_COMPARISON = getBestComparison();
private static Comparison getBestComparison() {
try {
Class<?> theClass = Class.forName(UNSAFE_COMPARATOR_NAME);
@SuppressWarnings("unchecked")
Comparison comparison = (Comparison) theClass.getEnumConstants()[0];
return comparison;
} catch (Throwable t) { // ensure we really catch *everything*
return lexicographicalComparisonJavaImpl();
}
}
private enum UnsafeComparison implements Comparison {
SINGLETON;
private final boolean littleEndian = ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN);
private final int UNSIGNED_MASK = 0xFF;
/*
* The following static final fields exist for performance reasons.
*
* In UnsignedBytesBenchmark, accessing the following objects via static
* final fields is the fastest (more than twice as fast as the Java
* implementation, vs ~1.5x with non-final static fields, on x86_32)
* under the Hotspot server compiler. The reason is obviously that the
* non-final fields need to be reloaded inside the loop.
*
* And, no, defining (final or not) local variables out of the loop still
* isn't as good because the null check on the theUnsafe object remains
* inside the loop and BYTE_ARRAY_BASE_OFFSET doesn't get
* constant-folded.
*
* The compiler can treat static final fields as compile-time constants
* and can constant-fold them while (final or not) local variables are
* run time values.
*/
static final Unsafe theUnsafe;
/** The offset to the first element in a byte array. */
static final int BYTE_ARRAY_BASE_OFFSET;
static {
theUnsafe = getUnsafe();
BYTE_ARRAY_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class);
// sanity check - this should never fail
if (theUnsafe.arrayIndexScale(byte[].class) != 1) {
throw new AssertionError();
}
}
/**
* Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package.
* Replace with a simple call to Unsafe.getUnsafe when integrating
* into a jdk.
*
* @return a sun.misc.Unsafe
*/
private static sun.misc.Unsafe getUnsafe() {
try {
return sun.misc.Unsafe.getUnsafe();
} catch (SecurityException tryReflectionInstead) {
}
try {
return java.security.AccessController.doPrivileged((java.security.PrivilegedExceptionAction<Unsafe>) () -> {
Class<Unsafe> k = Unsafe.class;
for (java.lang.reflect.Field f : k.getDeclaredFields()) {
f.setAccessible(true);
Object x = f.get(null);
if (k.isInstance(x)) {
return k.cast(x);
}
}
throw new NoSuchFieldError("the Unsafe");
});
} catch (java.security.PrivilegedActionException e) {
throw new RuntimeException("Could not initialize intrinsics",
e.getCause());
}
}
@Override
public int compare(byte[] o1, int offset1, int len1, byte[] o2, int offset2, int len2) {
int minLength = Math.min(len1, len2);
int minWords = minLength / Longs.BYTES;
/*
* Compare 8 bytes at a time. Benchmarking shows comparing 8 bytes at a
* time is no slower than comparing 4 bytes at a time even on 32-bit.
* On the other hand, it is substantially faster on 64-bit.
*/
for (int i = 0; i < minWords * Longs.BYTES; i += Longs.BYTES) {
long lw = theUnsafe.getLong(o1, BYTE_ARRAY_BASE_OFFSET + offset1 + (long) i);
long rw = theUnsafe.getLong(o2, BYTE_ARRAY_BASE_OFFSET + offset2 + (long) i);
long diff = lw ^ rw;
if (diff != 0) {
if (!littleEndian) {
return UnsignedLongs.compare(lw, rw);
}
// Use binary search
int n = 0;
int y;
int x = (int) diff;
if (x == 0) {
x = (int) (diff >>> 32);
n = 32;
}
y = x << 16;
if (y == 0) {
n += 16;
} else {
x = y;
}
y = x << 8;
if (y == 0) {
n += 8;
}
return (int) (((lw >>> n) & UNSIGNED_MASK) - ((rw >>> n) & UNSIGNED_MASK));
}
}
// The epilogue to cover the last (minLength % 8) elements.
for (int i = minWords * Longs.BYTES; i < minLength; i++) {
int result = UnsignedBytes.compare(o1[offset1 + i], o2[offset2 + i]);
if (result != 0) {
return result;
}
}
return len1 - len2;
}
}
private static Comparison lexicographicalComparisonJavaImpl() {
return PureJavaComparison.INSTANCE;
}
private enum PureJavaComparison implements Comparison {
INSTANCE;
@Override
public int compare(byte[] o1, int offset1, int len1, byte[] o2, int offset2, int len2) {
int minLength = Math.min(len1, len2);
for (int i = 0; i < minLength; i++) {
int result = UnsignedBytes.compare(o1[offset1 + i], o2[offset2 + i]);
if (result != 0) {
return result;
}
}
return len1 - len2;
}
}
}
}